package kayao.client.network.message;

import java.util.Date;
import java.util.Vector;


import kayao.client.data.SharedMemory;
import kayao.client.network.NetreadThread;
import kayao.client.network.NetwriteThread;
import kayao.client.network.SynchronizedTime;
import kayao.common.Client;
import kayao.common.KayaoValues;
import kayao.common.NetworkUtils;



public class MessageHandler {
	//The queues of messages, each index express the priority.
	protected static Vector<IncomingMessage> []mIncoming;
	protected static Object mIncomingLock=new Object();
	protected static Vector<Message> []mOutgoing;
	protected static Object mOutgoingLock=new Object();
	protected static Vector<Message> mResponse;
	protected static Object mResponseLock=new Object();
	protected static MessageThread mMessageThread=null;
	protected static NetwriteThread mNetwriteThread = null;
	protected static NetreadThread mNetreadThread = null;
	protected static int []mMsgStats = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // 20, [0] is for total
	protected static Date mStartTime;
	
	public static void initialize(NetwriteThread netwriteThread, NetreadThread netreadThread) {
		mIncoming = new Vector[3];
		mIncoming[0]=new Vector<IncomingMessage>();
		mIncoming[1]=new Vector<IncomingMessage>();
		mIncoming[2]=new Vector<IncomingMessage>();
		mOutgoing = new Vector[3];
		mOutgoing[0]=new Vector<Message>();
		mOutgoing[1]=new Vector<Message>();
		mOutgoing[2]=new Vector<Message>();
		mResponse=new Vector<Message>();
		mMessageThread=new MessageThread();
		mNetwriteThread = netwriteThread;
		mNetreadThread = netreadThread;
		mStartTime = new Date();
	}
	
	public static void start() {
		mMessageThread.start();
	}
	
	public static boolean terminate(){
		while(mMessageThread.terminate())
		mIncoming = null;
		mOutgoing = null;
		mResponse = null;
		return false;
	}
	
	public static void printStats(){
		System.out.println("============[ KAYAO STATISTICS ]=========================");
		System.out.println("PLAYING_REQUEST_MESSAGE: "+mMsgStats[KayaoValues.PLAYING_REQUEST_MESSAGE]);
		System.out.println("PLAYING_REQUEST_REPLY_MESSAGE: "+mMsgStats[KayaoValues.PLAYING_REQUEST_REPLY_MESSAGE]);
		System.out.println("KING_REQUEST_MESSAGE: "+mMsgStats[KayaoValues.KING_REQUEST_MESSAGE]);
		System.out.println("KING_REQUEST_REPLY_MESSAGE: "+mMsgStats[KayaoValues.KING_REQUEST_REPLY_MESSAGE]);
		System.out.println("SUSCRIPTION_REQUEST: "+mMsgStats[KayaoValues.SUSCRIPTION_REQUEST]);
		System.out.println("SUSCRIPTION_REQUEST_ANSWER: "+mMsgStats[KayaoValues.SUSCRIPTION_REQUEST_ANSWER]);
		System.out.println("SUSCRIPTION_REQUEST_NEGATIVE_ANSWER: "+mMsgStats[KayaoValues.SUSCRIPTION_REQUEST_NEGATIVE_ANSWER]);
		System.out.println("NEW_MATE_ARRIVED: "+mMsgStats[KayaoValues.NEW_MATE_ARRIVED]);
		System.out.println("STATE_UPDATE: "+mMsgStats[KayaoValues.STATE_UPDATE]);
		System.out.println("NEW_MATE_REPLY: "+mMsgStats[KayaoValues.NEW_MATE_REPLY]);
		System.out.println("LOGOUT_MSG: "+mMsgStats[KayaoValues.LOGOUT_MSG]);
		System.out.println("TEAM_COMMUNICATION: "+mMsgStats[KayaoValues.TEAM_COMMUNICATION]);
		System.out.println("TEAM_COMMUNICATION_REPLY: "+mMsgStats[KayaoValues.TEAM_COMMUNICATION_REPLY]);		
		System.out.println("WHAT_TIME_IS_IT_MESSAGE: "+mMsgStats[KayaoValues.WHAT_TIME_IS_IT_MESSAGE]);
		System.out.println("TIME_IS_MESSAGE: "+mMsgStats[KayaoValues.TIME_IS_MESSAGE]);
		System.out.println("DEAD_CLIENT: "+mMsgStats[KayaoValues.DEAD_CLIENT]);
		System.out.println("NEW_MISSION: "+mMsgStats[KayaoValues.NEW_MISSION]);
		System.out.println("UNSUBSCRIBE_MSG: "+mMsgStats[KayaoValues.UNSUBSCRIBE_MSG]);
		System.out.println("FLAG_COLLISION: "+mMsgStats[KayaoValues.FLAG_COLLISION]);
		System.out.println("");
		System.out.println("Total Runtime: "+SynchronizedTime.convertUptime((System.currentTimeMillis()-mStartTime.getTime())));
		System.out.println("Total number of packets: "+mMsgStats[0]);		
		System.out.println("=========================================================");
	}
	
	public static void statistics(int msg){
		mMsgStats[0] = (mMsgStats[0]+1);
		switch (msg) {
			case KayaoValues.PLAYING_REQUEST_MESSAGE: 				mMsgStats[KayaoValues.PLAYING_REQUEST_MESSAGE]++; break;
			case KayaoValues.PLAYING_REQUEST_REPLY_MESSAGE: 		mMsgStats[KayaoValues.PLAYING_REQUEST_REPLY_MESSAGE]++; break;
			case KayaoValues.KING_REQUEST_MESSAGE: 					mMsgStats[KayaoValues.KING_REQUEST_MESSAGE] = (mMsgStats[KayaoValues.KING_REQUEST_MESSAGE]+1); break;
			case KayaoValues.KING_REQUEST_REPLY_MESSAGE: 			mMsgStats[KayaoValues.KING_REQUEST_REPLY_MESSAGE]++; break;
			case KayaoValues.SUSCRIPTION_REQUEST: 					mMsgStats[KayaoValues.SUSCRIPTION_REQUEST]++; break;
			case KayaoValues.SUSCRIPTION_REQUEST_ANSWER: 			mMsgStats[KayaoValues.SUSCRIPTION_REQUEST_ANSWER]++; break;
			case KayaoValues.SUSCRIPTION_REQUEST_NEGATIVE_ANSWER: 	mMsgStats[KayaoValues.SUSCRIPTION_REQUEST_NEGATIVE_ANSWER]++; break;
			case KayaoValues.NEW_MATE_ARRIVED: 						mMsgStats[KayaoValues.NEW_MATE_ARRIVED]++; break;
			case KayaoValues.STATE_UPDATE: 							mMsgStats[KayaoValues.STATE_UPDATE]++; break;
			case KayaoValues.NEW_MATE_REPLY: 						mMsgStats[KayaoValues.NEW_MATE_REPLY]++; break;
			case KayaoValues.LOGOUT_MSG: 							mMsgStats[KayaoValues.LOGOUT_MSG]++; break;
			case KayaoValues.TEAM_COMMUNICATION: 					mMsgStats[KayaoValues.TEAM_COMMUNICATION]++; break;
			case KayaoValues.WHAT_TIME_IS_IT_MESSAGE: 				mMsgStats[KayaoValues.WHAT_TIME_IS_IT_MESSAGE]++; break;
			case KayaoValues.TIME_IS_MESSAGE: 						mMsgStats[KayaoValues.TIME_IS_MESSAGE]++; break;
			case KayaoValues.DEAD_CLIENT: 							mMsgStats[KayaoValues.DEAD_CLIENT]++; break;
			case KayaoValues.TEAM_COMMUNICATION_REPLY: 				mMsgStats[KayaoValues.TEAM_COMMUNICATION_REPLY]++; break;
			case KayaoValues.NEW_MISSION:			 				mMsgStats[KayaoValues.NEW_MISSION]++; break;
			case KayaoValues.UNSUBSCRIBE_MSG: 						mMsgStats[KayaoValues.UNSUBSCRIBE_MSG]++; break;
			case KayaoValues.FLAG_COLLISION: 						mMsgStats[KayaoValues.FLAG_COLLISION]++; break;
			
			default: 												System.out.println("MessageHandler.statistics: This should never happen, msgtype:"+msg); break;
		}
	}
	
	/**
	 * It sends a message saved in SharedMemory - outgoingMessage to all the teammates
	 * @return false if there isn't message to send
	 */
	public static boolean sendTeamCommunication()
	{
		synchronized(mOutgoingLock){
			String message=SharedMemory.getInstance().getOutgoingMessage();
			if(message==null){
				System.out.println("Sound not sent from sendTeamCommunication");
				return false;
		}
			else
			{
				for(int i=0;i<SharedMemory.getInstance().getTeamMates().length;i++)
				{
					Client mate=SharedMemory.getInstance().getTeamMates()[i];
					if(mate!=null && mate.getId()!=SharedMemory.getInstance().getMyself().getId()){
						insertOutgoingMessage(new TeamCommunication(mate.getIP(),3,5000,
								Message.PRIORITY_LOW,message));
					System.out.println("Sound sent to: "+mate.getIP());
				}
				}
				return true;
			}
		}
	}
	
	/**
	 * It gets the next Message from the correspondent queue.
	 * @param priority The priority of the queue.
	 * @return The message. Null if that queue is empty
	 */
	public static Message getMessageOutgoing(int priority) {
		synchronized(mOutgoingLock){
			if(priority == Message.PRIORITY_HIGH || priority == Message.PRIORITY_LOW 
					|| priority == Message.PRIORITY_MEDIUM  )
			{
				if(mOutgoing[priority].size()==0)
					return null;
				else
				{
					Message m=mOutgoing[priority].get(0);
					mOutgoing[priority].remove(0);
					return m;
				}
			}
		}
		return null;
	}
	public static IncomingMessage getMessageIncoming(int priority) {
		synchronized(mIncomingLock){
			if(priority == Message.PRIORITY_HIGH || priority == Message.PRIORITY_LOW 
					|| priority == Message.PRIORITY_MEDIUM  )
			{
				if(mIncoming[priority].size()==0)
					return null;
				else
				{
					IncomingMessage m=mIncoming[priority].get(0);
					mIncoming[priority].remove(0);
					return m;
				}
			}
		}
		return null;
	}

	/**
	 * It gets the next message in the waiting queue and delete it
	 */
	public static Message getNextMessageAwaitingReply() {
		synchronized(mResponseLock){
		if(mResponse.size()==0)
			return null;
		else
		{
			Message m=mResponse.get(0);
			return m;
		}
		}
	}
	public static boolean gotMessagesAwaitingReply() {
		synchronized(mResponseLock){
		if(mResponse.size()!=0)
			return true;
		return false;
		}
	}
	
	public static boolean gotMessagesToSend() {
		synchronized(mOutgoingLock){
		if(mOutgoing[0].size()!=0)
			return true;
		if(mOutgoing[1].size()!=0)
			return true;
		if(mOutgoing[2].size()!=0)
			return true;
		return false;
		}
	}

	public static boolean gotMessagesToRead() {
		synchronized(mIncomingLock){
		if(mIncoming[0].size()!=0)
			return true;
		if(mIncoming[1].size()!=0)
			return true;
		if(mIncoming[2].size()!=0)
			return true;
		return false;
		}
	}

	/**
	 * @return False if the write thread can go to sleep
	 * 		   True if there are messages waiting for reply or to be sent
	 */
	public static boolean gotMessagesInQueue() {
		synchronized(mOutgoingLock){
		if(mOutgoing[0].size()!=0)
			return true;
		if(mOutgoing[1].size()!=0)
			return true;
		if(mOutgoing[2].size()!=0)
			return true;
		}
		synchronized(mResponseLock){
		if(mResponse.size()!=0)
			return true;
		return false;
		}
	}

	public static void insertIncomingMessage( String ip, byte[] data) 
	{
		synchronized(mIncomingLock){
			if(NetworkUtils.insertByteArrayToInt(data, 0)!=KayaoValues.MAGIC_NUMBER)
			{
				System.out.println("[MessageHandler] -  Bad request from: " + ip + ". It failed the magicnumber");
				return;
			}
			byte type=data[4];
//			System.out.println("[MessageHandler] - Type: "+ type+" came. Inserting in incoming queue.");
			if(type==KayaoValues.NEW_MATE_ARRIVED)
				//new mate arrive, no counter id->0
				mIncoming[Message.PRIORITY_LOW].add(new IncomingMessage(ip,data,0));
			else
			{
				//Get the priority
				int priority=Message.PRIORITY_MEDIUM;
				if(type ==KayaoValues.LOGOUT_MSG || type ==KayaoValues.TEAM_COMMUNICATION){
					System.out.println("Sound Team Com put in incoming");
					priority=Message.PRIORITY_LOW;
				}
				else if(type==KayaoValues.STATE_UPDATE || type==KayaoValues.UNSUBSCRIBE_MSG)
					priority=Message.PRIORITY_HIGH;
				//insert in the good one
				mIncoming[priority].add(new IncomingMessage(ip,data,NetworkUtils.insertByteArrayToInt(data,5)));
			}

			synchronized(mMessageThread) {
				mMessageThread.notify();
			}
		}
	}
	
	public static void insertOutgoingMessage(Message outgoingMessage) {
		synchronized(mOutgoingLock){
			mOutgoing[outgoingMessage.getPriority()].add(outgoingMessage);
			synchronized(NetwriteThread.LOCK) {
				NetwriteThread.LOCK.notify();
			}
			//System.out.println("Making an inerrupt if sleeping from insertOutgoingMessage");
			mNetwriteThread.interrupt();
		}
	}
	
	/**
	 * It makes a sorted insertion in the awaiting response list by time
	 * @param m the message to insert
	 * @param ip 
	 */
	public static void insertAwaitingResponseMessage(Message m) {
		synchronized(mResponseLock){
			for(int i=0;i<mResponse.size();i++)
			{
				if(mResponse.get(i).getClass().equals(m.getClass()) && 
						m.getTargetAddress().equals(mResponse.get(i).getTargetAddress()))
				{
					if(m.getRetriesLeft()<mResponse.get(i).getRetriesLeft())return;
					m.setTimeout(mResponse.get(i).getTimeout());
					mResponse.set(i, m); //timeout??
					return;
				}
			}
			for(int i=0;i<mResponse.size();i++)
			{
				if(mResponse.get(i).mTimeout>m.getTimeout())
				{
					mResponse.insertElementAt(m, i);
					return;
				}
			}
			mResponse.add(m);
			//System.out.println("Making an inerrupt if sleeping from insertAwaitingResponseMessage");
			mNetwriteThread.interrupt();
		}
	}
	
	public static void removeAwatingMessage(int c) 
	{
		synchronized(mResponseLock){	
			for(int i=0;i<MessageHandler.mResponse.size();i++)
			{
				if(MessageHandler.mResponse.get(i).mMessageId==c)
				{
					MessageHandler.mResponse.remove(i);
					System.out.println("[MessageHandler] - MSG with int: "+ c+" deleted from queue of awaiting.");
					return;
				}
			}
		}
	}
	/**
	 * An answer has come, we have to delete it from the mResponse queue
	 * @param m: the message
	 */
	public static void removeAwatingMessage(IncomingMessage m) 
	{
		synchronized(mResponseLock){
			for(int i=0;i<MessageHandler.mResponse.size();i++)
			{
				if(MessageHandler.mResponse.get(i).mMessageId==m.getCounter())
				{
					MessageHandler.mResponse.remove(i);
					System.out.println("[MessageHandler] - MSG with id: "+ m.getCounter()+" deleted from queue of awaiting.");
					return;
				}
			}
			//System.out.println("[MessageHandler] - MSG with id: "+ m.getCounter()+" COULDN'T be deleted from queue of awaiting.");
		}
	}

	/**
	 * Get awaiting message from the mResponse queue
	 * and delete it from the mResponse
	 * @param k: message id of packet
	 */
	public static Message getAwaitingMessage(int k)
	{
		synchronized(mResponseLock){

			for(int i=0;i<MessageHandler.mResponse.size();i++)
			{
				//System.out.println("MessageHandler.getAwaitingMessage: if "+MessageHandler.mResponse.get(i).mMessageId+"=="+k);
				if(MessageHandler.mResponse.get(i).mMessageId==k)
				{
					Message msg = MessageHandler.mResponse.get(i);
					MessageHandler.mResponse.remove(i);
					return msg;
				}
			}
			return null;
		}
	}

	public static void setLogoutCountInNetwriteThread(int x){
		mNetwriteThread.setLogoutCount(x);
	}
	
	public static void decreaseLogoutCounter(){
		mNetwriteThread.decreaseLogoutCounter();
	}

	public static void removeSubscriptionFromQueues(String ip) {
		synchronized(mResponseLock){

			for(int i=0;i<mResponse.size();i++)
			{
				if(mResponse.get(i).getTargetAddress().equals(ip) && 
					mResponse.get(i) instanceof SubscriptionRequestMessage)
				{
					mResponse.remove(i);
					System.out.println("[MessageHandler] - Removing Subscription message in the response queue for losing interest in client.");
				}
			}
			}
		synchronized(mOutgoingLock){

			for(int i=0;i<mOutgoing[Message.PRIORITY_MEDIUM].size();i++)
			{
				if(mOutgoing[Message.PRIORITY_MEDIUM].get(i).getTargetAddress().equals(ip) && 
						mOutgoing[Message.PRIORITY_MEDIUM].get(i) instanceof SubscriptionRequestMessage)
				{
					mOutgoing[Message.PRIORITY_MEDIUM].remove(i);
					System.out.println("[MessageHandler] - Removing Subscription message in the outgoing queue for losing interest in client.");
				}
			}
			}
		
	}

}
